
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.*;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;



public class Servidor implements Runnable {

    private byte[] dados;
    private int len = 2048;
    public static TreeMap <Integer,byte[]> totaldados;
    public TreeSet <PacoteUDP> pacotes;
    private int flag=0;
    private static InetAddress iPAddress;
    private int porta = JDialogConfig.porta;
    private int janela= 1;
    private int nrecebidos=0;
    private int control=0;
    
    private int janelaaux;


     public void run(){
        Servidor servidor = new Servidor();
        try {
            try {
                servidor.liga();
                System.out.println("Sem mais tarefas...");
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (IOException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }



    public void guardaEmficheiro() throws FileNotFoundException, IOException {
        Ficheiro f = Ficheiro.bytesToFicheiro(dados);

        if (f != null)
        {
            FileOutputStream fos;
            fos = new FileOutputStream(f.getName());
            fos.write(f.getBuffer());
            fos.close();
        }
    }

    public int tamanhoTotal(TreeMap<Integer,byte[]> data){
        int t=0;

        for(byte[] d: data.values()){
            t+=d.length;
            }

        return t;
    }

    public void guardaDados(int p,TreeMap<Integer,byte[]>data ){
 
        int i,j=0;

        for(byte[] b:data.values()){
               i=0;
             while(i<b.length){
                dados[j]=b[i];
                j++;
                i++;
            }
         }
    }

    public int fazLigacao(DatagramSocket servidor) throws IOException{

        byte[] x = new byte[len];
        int ligacao =1;
        int prox=0;
        int pacoteN=0;
  
        PacoteUDP pLigacao;

        x = new byte[len];
        DatagramPacket datagramPedido = new DatagramPacket(x,x.length);

        int receivedPort = porta;
        InetAddress receivedIPAddress = iPAddress;
        while(ligacao!=0){

           try
           {
            System.out.println("---> À espera da conexão de um cliente... ");
            servidor.receive(datagramPedido);

            receivedIPAddress = datagramPedido.getAddress();
            receivedPort = datagramPedido.getPort();
            String sentence = new String(datagramPedido.getData());
            System.out.println ("DE: " + receivedIPAddress + ":" + receivedPort + " Mensagem: " + sentence);

            
            porta = receivedPort;
            iPAddress = receivedIPAddress;
            
           }
           catch (Exception ex)
           {
              
           }

            pLigacao = PacoteUDP.bytesToPacketUDP( datagramPedido.getData());

            if(pLigacao != null && pLigacao.getType()==2 && pLigacao.getID()!=-1){
            
                ligacao=0; 
                pacoteN= pLigacao.getID();
           
                iPAddress = InetAddress.getByName(JDialogConfig.getIP());
                PacoteUDP ack = new PacoteUDP();
                ack.setType(2);
            
                byte[] y = PacoteUDP.divideEmBytes(ack);

                DatagramPacket resposta = new DatagramPacket(y, y.length, receivedIPAddress, receivedPort);

                System.out.println("---> a enviar um ack...");
                servidor.send(resposta);
                System.out.println("---> ack enviado");

            }
        }

        return pacoteN;
    }


public int terminaLigacao(DatagramSocket servidor) throws IOException{

    byte[]  dadosReceber =new byte[len];
    int ligacao =1;
    PacoteUDP p;
    int e =0;
    int tentativas=0;
    int nPacote=0;

    while(ligacao!=0 && tentativas<10){
   
        PacoteUDP a = new PacoteUDP();
        a.setType(2);

        byte[] dadosEnviar= PacoteUDP.divideEmBytes(a);


        DatagramPacket resposta = new DatagramPacket(dadosEnviar, dadosEnviar.length, iPAddress, porta);

        servidor.send(resposta);

        dadosReceber = new byte[len];

        try{
        DatagramPacket pedidoLigacao = new DatagramPacket(dadosReceber, dadosReceber.length);

        servidor.receive( pedidoLigacao);

        p = PacoteUDP.bytesToPacketUDP( pedidoLigacao.getData());

        e = p.getType();
        nPacote = p.getID();
        } catch (Exception ex) {tentativas++;}
    
        if(e==2 && nPacote!=-1){ligacao=0; }

    }
    return tentativas;
}


public int proximo(TreeSet<PacoteUDP> dados, int n){

    for(PacoteUDP p : dados){
       if(p.getID()== n){
           n++;
           totaldados.put(p.getID(),p.getData());
           nrecebidos++;
           control++;}
    }

    return n;
}



 public void liga() throws IOException, ClassNotFoundException{

   
    totaldados = new TreeMap<Integer, byte[]>();
    int c=0;
    DatagramSocket servidor= new DatagramSocket(porta);
    byte[] r = new byte[len];
    int size=0;
    int ligacao =1;
    int prox =0;
    int tipo =0;
    int p;
    int tentativas=0;

    p=fazLigacao(servidor);
    servidor.connect(iPAddress, porta);

    servidor.setSoTimeout(1000);



    while(flag==0 && tentativas <20) {

        pacotes= new TreeSet<PacoteUDP>();

        int i =0;
        janelaaux=janela;
        while(i<janela && nrecebidos < p+1){

            DatagramPacket pedido = new DatagramPacket(r, r.length);
            PacoteUDP packet;

            try
            {
                servidor.receive(pedido);
            }
            catch (Exception ex)
            {
              
            }

            packet = PacoteUDP.bytesToPacketUDP(pedido.getData());

            if(packet != null && packet.getType()!=2){
                try   {System.out.println("--> Servidor a receber dados de " + pedido.getAddress().toString() + ':' + pedido.getPort() + " data: " + packet.getData().toString());}
                catch (Exception ex){}

                pacotes.add(packet);
                janelaaux=janela*2;
            }

            else {
                try   {System.out.println("--> Servidor recebeu outro pacote " + pedido.getAddress().toString() + ':' + pedido.getPort());}
                catch (Exception ex){}
                tipo = 2; 
                i=5;
                if(janelaaux>janela) {janelaaux = janela/2;}
            }
            
            i++;
        }

        prox = proximo(pacotes, prox);


        if (tipo!=2){
            PacoteUDP ack = new PacoteUDP();
            ack.setType(3);

            ack.setID(prox);

            byte[] dadosEnviar= PacoteUDP.divideEmBytes(ack);

            DatagramPacket resposta = new DatagramPacket(dadosEnviar, dadosEnviar.length, iPAddress, porta);

            servidor.send(resposta);
        }

        if (tipo==2){
            flag=1;tentativas=0; 
            tentativas=terminaLigacao(servidor);
        }
    }

    if(tentativas <10){

            size = tamanhoTotal(totaldados);
            dados = new byte[size];
            guardaDados(size,totaldados);

            try{
                System.out.println( "A guardar em ficheiro...");
                guardaEmficheiro();
            }
            catch(Exception ex){

               System.out.println( "Envio foi Cancelado!");
               Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
    }
            
    else{
        System.out.println( "Falha ao terminar a ligação!");
    }
     
}

}








